﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RW.Data.Graph;
using RW.Data.Objects;
using QuickGraph.Algorithms;

namespace RW.Data
{
    public class Query3
    {
        public Dictionary<string, bool> Conditions { get; set; }
        public Dictionary<string, bool> Target { get; set; }

        private MultiGraph graph;

        public Query3(MultiGraph multiGraph)
        {
            Conditions = new Dictionary<string, bool>();
            Target = new Dictionary<string, bool>();
            graph = multiGraph;
        }

        public string Compute(bool isAlways)
        {
            try
            {
                List<State> startingVertices;
                if (Conditions.Count == 0) //jesli user nie okresli w inpucie to brane są tylko initial states (zgodnie z opisem w dokumentacji).
                    startingVertices = graph.Vertices.Where(v => v.IsInitial == true).ToList();
                else
                    startingVertices = graph.Vertices.Where(v => v.SatisfiesConditions(Conditions)).ToList();

                //przypadek szczególny nr 1: infinity na samym początku szukania.
                if (isAlways)
                    if (CheckInfinite(startingVertices))
                        //Któryś z wierzchołków początkowych ma pętlę do samego siebie - najdłuższa ścieżka jest nieskończona.
                        return "infinity! /o\\";
                //***


                var target = graph.Vertices.Single(v => v.Equals(Target));

                //przypadek szczególny nr 3: startingVertices zawierają target
                List<State> startingVerticesWithStartingCost = new List<State>();
                List<int> startingCosts = new List<int>();
                int costOfTargetToTargetLoopWithIsAlwaysEqualsFalse = int.MaxValue;
                string actionNameOfTargetToTargetLoopWithIsAlwaysEqualsFalse = string.Empty;
                if (startingVertices.Contains(target))
                {
                    startingVertices.Remove(target);
                    var selfLoop = false;
                    foreach (var edge in graph.OutEdges(target))
                    {
                        if (edge.Target != target)
                        {
                            startingVerticesWithStartingCost.Add(edge.Target);
                            startingCosts.Add(edge.ActionCost);
                        }
                        else
                            selfLoop = true;
                    }
                    //przypadek szczególny nr 4: query typu exist i target zawiera pętle do samego siebie - zapamiętujemy koszt i porównamy ze znalezionym na końcu.
                    //nie musimy sprawdzać dla isAlways==true bo przypadek szczególny nr1 załatwia ten przypadek.
                    if (!isAlways && selfLoop)
                    {
                        var edges = graph.OutEdges(target).Where(e => e.Target == target);
                        foreach (var e in edges)
                            if (costOfTargetToTargetLoopWithIsAlwaysEqualsFalse > e.ActionCost)
                            {
                                costOfTargetToTargetLoopWithIsAlwaysEqualsFalse = e.ActionCost;
                                actionNameOfTargetToTargetLoopWithIsAlwaysEqualsFalse = String.Concat("START -> ", e.ActionName, " -> ");
                            }
                    }
                }

                //obsługa dijkstry
                Func<MultiGraphEdge, double> distances;
                if (isAlways)
                    distances = e => 1 / (e.ActionCost + 1);
                else
                    distances = e => e.ActionCost;
                //***

                //starting with cost (2)
                IEnumerable<MultiGraphEdge> path2;
                string pathDescription2 = "START -> ";
                string currentPathDesc2 = string.Empty;
                int sum2 = 0;
                int currentSum2 = -1;
                for (int i = 0; i < startingVerticesWithStartingCost.Count; i++)
                {
                    var tryGetPathWithStartingCost = graph.ShortestPathsDijkstra(distances, startingVerticesWithStartingCost[i]);
                    if (tryGetPathWithStartingCost(target, out path2))
                    {
                        sum2 = startingCosts[i];
                        foreach (var e in path2)
                        {
                            //przypadek szczególny nr 2: infinity wewnątrz ścieżki.
                            if (isAlways && graph.OutEdges(e.Source).Any(edge => edge.Target == e.Source))
                                //Na ścieżce do celu znaleziono wierzchołek z pętlą do samego siebie - najdłuższa ścieżka jest nieskończona.
                                return "infinity! /o\\";
                            //***
                            pathDescription2 += String.Concat(e.ActionName, " -> ");
                            sum2 += e.ActionCost;
                        }
                        if (!isAlways)
                        {
                            if (currentSum2 == -1)//pierwsza znaleziona
                            {
                                currentSum2 = sum2;
                                currentPathDesc2 = pathDescription2;
                            }
                            if (currentSum2 > sum2)
                            {
                                currentSum2 = sum2;
                                currentPathDesc2 = pathDescription2;
                            }
                        }
                        else
                            if (currentSum2 < sum2)
                            {
                                currentSum2 = sum2;
                                currentPathDesc2 = pathDescription2;
                            }
                    }
                    sum2 = 0;
                    pathDescription2 = "START -> ";
                }
                //***

                //starting without cost (1)
                IEnumerable<MultiGraphEdge> path;
                string pathDescription = "START -> ";
                string currentPathDesc = string.Empty;
                int sum = 0;
                int currentSum = -1;

                for (int i = 0; i < startingVertices.Count; i++)
                {
                    var tryGetPath = graph.ShortestPathsDijkstra(distances, startingVertices[i]);
                    if (tryGetPath(target, out path))
                    {
                        foreach (var e in path)
                        {
                            //przypadek szczególny nr 2: infinity wewnątrz ścieżki.
                            if (isAlways && graph.OutEdges(e.Source).Any(edge => edge.Target == e.Source))
                                //Na ścieżce do celu znaleziono wierzchołek z pętlą do samego siebie - najdłuższa ścieżka jest nieskończona.
                                return "infinity! /o\\";
                            //***
                            pathDescription += String.Concat(e.ActionName, " -> ");
                            sum += e.ActionCost;
                        }
                        if (!isAlways)
                        {
                            if (currentSum == -1)//pierwsza znaleziona
                            {
                                currentSum = sum;
                                currentPathDesc = pathDescription;
                            }
                            if (currentSum > sum)
                            {
                                currentSum = sum;
                                currentPathDesc = pathDescription;
                            }
                        }
                        else
                            if (currentSum < sum)
                            {
                                currentSum = sum;
                                currentPathDesc = pathDescription;
                            }
                    }
                    sum = 0;
                    pathDescription = "START -> ";
                }
                //***
                                
                if (currentSum < 0 && currentSum2 < 0) //wszystkie szukania ścieżki zawiodły
                {
                    if (costOfTargetToTargetLoopWithIsAlwaysEqualsFalse == int.MaxValue)
                        //return "error: Dijkstra failed to find a path!";
                        return "-1";
                    else
                        return String.Concat(costOfTargetToTargetLoopWithIsAlwaysEqualsFalse.ToString(), "\n", actionNameOfTargetToTargetLoopWithIsAlwaysEqualsFalse, "END");
                }

                sum = currentSum;
                pathDescription = currentPathDesc;
                if (currentSum2 >= 0)
                    if (isAlways)
                    {
                        if (sum < currentSum2)
                        {
                            sum = currentSum2;
                            pathDescription = currentPathDesc2;
                        }
                    }
                    else
                    {
                        if (sum == -1 || sum > currentSum2)
                        {
                            sum = currentSum2;
                            pathDescription = currentPathDesc2;
                        }
                    }

                //obsługa specjalnego przypadku nr4
                if (costOfTargetToTargetLoopWithIsAlwaysEqualsFalse <= sum)
                {
                    pathDescription = actionNameOfTargetToTargetLoopWithIsAlwaysEqualsFalse;
                    sum = costOfTargetToTargetLoopWithIsAlwaysEqualsFalse;
                }
                //***
                pathDescription += "END";
                return String.Concat(sum, "\n", pathDescription);
            }
            catch (Exception)
            {
                return "error";
            }
        }

        private bool CheckInfinite(List<State> startingVertices)
        {
            foreach (var v in startingVertices)
            {
                if (graph.OutEdges(v).Any(edge => edge.Target == v))
                    //Na ścieżce do celu znaleziono wierzchołek z pętlą do samego siebie - najdłuższa ścieżka jest nieskończona.
                    return true;
            }
            return false;
        }
    }
}
